using System;
using System.Collections.Generic;
using System.Text;
using Algorithms.Numerical.Impl;

namespace Algorithms.Numerical
{
    public abstract class Matrix
    {
        public abstract int Height
        {
            get;
        }

        public abstract int Width
        {
            get;
        }

        public abstract double this[int y, int x]
        {
            get;
            set;
        }

        public void Assign(double[,] values)
        {
            if (values.GetLength(0) != this.Height || values.GetLength(1) != this.Width)
                throw new ArgumentException();
            for (int y = 0; y < this.Height; y++)
                for (int x = 0; x < this.Width; x++)
                    this[y, x] = values[y, x];
        }

        public void Assign(Matrix m)
        {
            if (m.Height != this.Height || m.Width != this.Width)
                throw new ArgumentException();
            for (int y = 0; y < this.Height; y++)
                for (int x = 0; x < this.Width; x++)
                    this[y, x] = m[y, x];
        }

        /*
         *  Make deep copy of matrix. 
         */
        public Matrix Copy()
        {
            Matrix m = MatrixFactory.Instance.MakeZero(this.Height, this.Width);
            m.Assign(this);
            return m;
        }

        public Matrix TransposedView
        {
            get
            {
                return new TransposedViewDecorator(this);
            }
        }

        public Matrix SubmatrixView(int y, int x, int height, int width)
        {
            return new SubmatrixViewDecorator(this, y, x, height, width);
        }

        public Matrix SelectionView(int[] rowIndexes, int[] colIndexes)
        {
            return new SelectionViewDecorator(this, rowIndexes, colIndexes);
        }

        public Matrix ChangedRowsView(int i, int j)
        {
            if (i < 0 || j < 0 || i >= this.Height || j > this.Height)
                throw new ArgumentException();
            int[] a = range(this.Height);
            a[i] = j;
            a[j] = i;
            return SelectionView(a, null);
        }

        public Matrix ChangedCoumnsView(int i, int j)
        {
            if (i < 0 || j < 0 || i >= this.Width || j > this.Width)
                throw new ArgumentException();
            int[] a = range(this.Width);
            a[i] = j;
            a[j] = i;
            return SelectionView(null,a);
        }

        public override bool Equals(object obj)
        {
            if (! (obj is Matrix))
                return false;
            Matrix m = (Matrix)obj;
            if (this.Height != m.Height || this.Width != m.Width)
                return false;
            for (int y = 0; y < this.Height; y++)
                for (int x = 0; x < this.Width; x++)
                    if (this[y, x] != m[y, x])
                        return false;
            return true;            
        }


        public void Add(Matrix m)
        {
            MatrixFactory.Instance.Sum(this, m, true);
        }

        public void Mul(Matrix m)
        {
            Matrix r = MatrixFactory.Instance.Mult(1, this, m);
            this.Assign(r);
        }

        public void Mul(double s)
        {
            MatrixFactory.Instance.Mult(s, this, true);
        }

        public override string ToString()
        {
            string s = "[ \n";
            for (int y = 0; y < Height; y++)
            {
                s += "  [";
                for (int x = 0; x < Width; x++)
                {
                    s += this[y, x].ToString("N");
                    if (x < (Width - 1))
                        s += ", ";
                }
                s += " ]\n";
            }

            s += "]\n";

            return s;
        }


        private int[] range(int limit)
        {
            int[] r = new int[limit];
            for (int i = 0; i < limit; i++)
                r[i] = i;
            return r;
        }
    }
}
